class App { constructor( canvasId ) { this.cc = document.getElementById( canvasId ).getContext( "2d" ); let scale = 2; let horizon = 0.9; let transX = this.cc.canvas.width / scale / 2; let transY = -this.cc.canvas.height * horizon / scale; this.cc.scale( scale, -scale ); this.cc.translate( transX, transY ); this.clearRect = function() { this.cc.clearRect( -this.cc.canvas.width / scale / 2, this.cc.canvas.height * horizon / scale, this.cc.canvas.width / scale, - this.cc.canvas.height / scale ); } /* let slider = document.createElement( "input" ); slider.setAttribute( "type", "range" ); slider.setAttribute( "id", "range1" ); slider.setAttribute( "autocomplete", "on" ); slider.setAttribute( "value", 0 ); slider.setAttribute( "min", 0 ); slider.setAttribute( "step", 1 ); slider.oninput = function( e ) { this.testpose( e.target.value ); }.bind( this ); slider.style.margin = "auto"; slider.style.display = "block"; this.cc.canvas.after( slider ); */ let div = document.createElement( "div" ); this.cc.canvas.after( div ); div.outerHTML = ( function() { /*
posing: test
*/ } ).toString().match( /\/\*([\s\S]*)\*\// )[ 1 ]; document.getElementById( "range1" ).oninput = function( e ) { //押下時 this.testpose( e.target.value ); let name = Object.keys( this.poses )[ e.target.value ]; document.getElementById( "sliderselected" ).innerHTML = name; }.bind( this ); this.svcs = new Array(); let world = new SVC_Part( "world" ); world.addJoint( "世界>腹", 0, 80 ); world.addJoint( "世界>キャラ1", 0, 0 ); world.imgd.visibility = true; world.imgd.type = "horizon"; world.debug1 = true; let hara = new SVC_Part( "腹" ); hara.imgd.w = 18; hara.imgd.h = 15; hara.imgd.x = -12.5; hara.imgd.y = -15; hara.addJoint( "腹>胸", 0, 0 ); hara.addJoint( "腹>腰", 0, -12 ); let mune = new SVC_Part( "胸" ); mune.imgd.w = 20; mune.imgd.h = 20; mune.imgd.x = -12.5; mune.imgd.y = -5; mune.addJoint( "胸>首", 1, 14 ); mune.addJoint( "胸>左上腕", 1, 14 ); let jouwanL = new SVC_Part( "左上腕" ); jouwanL.imgd.w = 10; jouwanL.imgd.h = 23; jouwanL.imgd.x = -5; jouwanL.imgd.y = -23; jouwanL.addJoint( "左上腕>左前腕", 2, -20 ); let zenwanL = new SVC_Part( "左前腕" ); zenwanL.imgd.w = 10; zenwanL.imgd.h = 23; zenwanL.imgd.x = -7; zenwanL.imgd.y = -21; zenwanL.addJoint( "左前腕>左手", -2, -20 ); zenwanL.debug1 = true; let teL = new SVC_Part( "左手" ); teL.imgd.w = 10; teL.imgd.h = 10; teL.imgd.x = -5; teL.imgd.y = -10; teL.addJoint( "つかみ", 0, -5 ); teL.debug1 = true; let kubi = new SVC_Part( "首" ); kubi.imgd.w = 10; kubi.imgd.h = 10; kubi.imgd.x = -6; kubi.imgd.y = -2; kubi.addJoint( "首>頭", 0, 6 ); let atama = new SVC_Part( "頭" ); atama.imgd.w = 20; atama.imgd.h = 20; atama.imgd.x = -13; atama.imgd.y = -1; let kosi = new SVC_Part( "腰" ); kosi.imgd.w = 20; kosi.imgd.h = 15; kosi.imgd.x = -12.5; kosi.imgd.y = -13; kosi.addJoint( "腰>左もも", -2.5, -10 ); kosi.addJoint( "腰>右もも", -2.5, -10 ); let momoL = new SVC_Part( "左もも" ); momoL.imgd.w = 18; momoL.imgd.h = 30; momoL.imgd.x = -10; momoL.imgd.y = -28; momoL.addJoint( "左もも>左すね", -2.5, -27.5 ); let suneL = new SVC_Part( "左すね" ); suneL.imgd.w = 18; suneL.imgd.h = 30; suneL.imgd.x = -6.5; suneL.imgd.y = -28; suneL.addJoint( "左すね>左かかと", 2.5, -25 ); suneL.debug1 = true; let kakatoL = new SVC_Part( "左かかと" ); kakatoL.imgd.w = 20; kakatoL.imgd.h = 10; kakatoL.imgd.x = -12; kakatoL.imgd.y = -6; kakatoL.addJoint( "左かかと>左つま先", -10, -2 ); kakatoL.addJoint( "接地", -10, -5 ); kakatoL.debug1 = true; let tumasakiL = new SVC_Part( "左つま先" ); tumasakiL.imgd.w = 10; tumasakiL.imgd.h = 7; tumasakiL.imgd.x = -10; tumasakiL.imgd.y = -4; tumasakiL.addJoint( "接地", -10, 0 ); tumasakiL.debug1 = true; //build. world.connect( "世界>腹", hara ); hara.connect( "腹>胸", mune ); mune.connect( "胸>首", kubi ); mune.connect( "胸>左上腕", jouwanL ); jouwanL.connect( "左上腕>左前腕", zenwanL ); zenwanL.connect( "左前腕>左手", teL ); kubi.connect( "首>頭", atama ); hara.connect( "腹>腰", kosi ); kosi.connect( "腰>左もも", momoL ); momoL.connect( "左もも>左すね", suneL ); suneL.connect( "左すね>左かかと", kakatoL ); kakatoL.connect( "左かかと>左つま先", tumasakiL ); let tick = 3.14 / 10; this.poses = { chokuritu : function() { hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" ); this.reset(); }, kagami : function() { hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" ); this.reset(); atama.rotation = tick * -.5; kubi.rotation = tick * 0; mune.rotation = tick * .5; kosi.rotation = tick * -1; momoL.rotation = tick * -1.5; suneL.rotation = tick * 2.5; kakatoL.rotation = tick * -1.5; tumasakiL.rotation = tick * 0; jouwanL.rotation = tick * -4; zenwanL.rotation = tick * -2; teL.rotation = tick * -1; }, senobi : function() { hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" ); this.reset(); atama.rotation = tick * -1.5; kubi.rotation = tick * -1; mune.rotation = tick * -0.5; kosi.rotation = tick * 0.5; momoL.rotation = tick * 0.5; suneL.rotation = tick * 0; kakatoL.rotation = tick * 2.5; tumasakiL.rotation = tick * -3; jouwanL.rotation = tick * -12; zenwanL.rotation = tick * 0; teL.rotation = tick * -1; }, bridge : function() { hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" ); this.reset(); atama.rotation = tick * -1; kubi.rotation = tick * -2; mune.rotation = -3.14 /10; kosi.rotation = 3.14 /10; momoL.rotation = 3.14 /6; suneL.rotation = 3.14 /4; kakatoL.rotation = 3.14 /4; tumasakiL.rotation = -3.14 /4; jouwanL.rotation = tick * -13; zenwanL.rotation = tick * 0; teL.rotation = tick * 0; }, sakadachi : function() { hara.adjustByThisStop( teL, "つかみ", world ); this.reset(); atama.rotation = tick * -1; kubi.rotation = tick * -2; mune.rotation = tick * -0.5; kosi.rotation = tick * 0.5; momoL.rotation = tick * 0; suneL.rotation = tick * 0; kakatoL.rotation = 3.14 /4; tumasakiL.rotation = tick * 1; jouwanL.rotation = tick * -8.85; zenwanL.rotation = tick * 0; teL.rotation = tick * -0.5; }, }; //debug. テスト用スライダの最大値をこのposesの最大値にする let s = document.getElementById( "range1" ); s.setAttribute( "max", Object.keys( this.poses ).length - 1 ); this.svcs.push( world ); this.testpose( s.value ); } start() { if( 1 ) { this.draw( this.cc ); } if( 0 ) { this.timerId = setInterval( function() { this.draw( this.cc ); this.flg = ! this.flg; }.bind( this ), 500 ); } } stop() { clearInterval( this.timerId ); } draw( cc ) { this.clearRect(); for( let i = 0; i < this.svcs.length; i++ ) { let svc = this.svcs[ i ]; svc.draw( cc ); } } reset() { for( let i = 0; i < this.svcs.length; i++ ) { let svc = this.svcs[ i ]; svc.reset(); } } calc() { for( let i = 0; i < this.svcs.length; i++ ) { let svc = this.svcs[ i ]; svc.calc(); } } testpose( value ) { let name = Object.keys( this.poses )[ value ]; this.poses[ name ].call( this ); this.calc(); console.log( name ); this.draw( this.cc ); } }//App class SVC_Part { constructor( title ) { this.title = title; this.jointHash = new Object(); this.rotation = 0; this.adjustFrom = null; this.adjustFromJointName = null; this.adjustTo = null; this.adjustToJointName = null; this.abs = new Object(); this.parent = null; this.parentJoint = null; this.children = new Array(); this.imgd = new TestRect( "rect" ); this.debug1 = false; } addJoint( name, x, y ) { let joint = new Object(); joint.name = name; joint.x = x; joint.y = y; this.jointHash[ name ] = joint; } //---connect //他のSVC_Partを自身のJointへ接続 connect( jointName, part ) { this.children.push( part ); part.parent = this; //check. if( ! this.jointHash[ jointName ] ) { alert( "undefined key '" + jointName + "' in jointHash" ); } part.parentJoint = this.jointHash[ jointName ]; } //たとえば、足を地面に接地する等 adjust( adjustFrom, adjustFromJointName, adjustTo, adjustToJointName ) { this.adjustFrom = adjustFrom; this.adjustFromJointName = adjustFromJointName; this.adjustTo = adjustTo; this.adjustToJointName = adjustToJointName; } adjustByThisStop( adjustFrom, adjustFromJointName, adjustTo ) { //check. calc()が行われる前はabs.jointHashは未作成 if( ! adjustFrom.abs.jointHash ) { alert( ( function() {/* 最初の描画でadjustByThisStop()を行おうとしましたが、 adjustByThisStop()は「前回の描画をもとにして adjust()を行うメソッド」なので実行できません。 */} ).toString().match( /\/\*\s*([\s\S]*)\s*\*\// )[ 1 ].replace( /\t/g, "" ) ); return; } let joint = adjustFrom.abs.jointHash[ adjustFromJointName ]; adjustTo.addJoint( "this stop", joint.x, joint.y ); this.adjust( adjustFrom, adjustFromJointName, adjustTo, "this stop" ); } reset() { this.rotation = 0; //子 for( let i = 0; i < this.children.length; i++ ) { let child = this.children[ i ]; child.reset(); } } calc() { //自身の値の絶対座標を求める。 if( ! this.parent ) { //親がない場合は this.abs.x = 0; this.abs.y = 0; this.abs.rotation = this.rotation; } else { //親がある場合は //check. if( ! this.parentJoint ) { alert( "undefined parentJoint at " + this.title ); } let parentJointAbs = this.parent.abs.jointHash[ this.parentJoint.name ]; this.abs.x = parentJointAbs.x; this.abs.y = parentJointAbs.y; this.abs.rotation = this.parent.abs.rotation + this.rotation; } //各jointの絶対座標を求める。 this.abs.jointHash = new Object(); for( let name in this.jointHash ) { let j = this.jointHash[ name ]; let res = this.mathRotate( j.x, j.y, this.abs.rotation ); this.abs.jointHash[ name ] = { x : this.abs.x + res.X, y : this.abs.y + res.Y, } } //子もcalc() for( let i = 0; i < this.children.length; i++ ) { let child = this.children[ i ]; child.calc(); } //---接地 if( this.adjustFrom ) { let from = this.adjustFrom.abs.jointHash[ this.adjustFromJointName ]; let to = this.adjustTo.abs.jointHash[ this.adjustToJointName ]; let adjustX = to.x - from.x; let adjustY = to.y - from.y; let adjustRotation = this.adjustTo.abs.rotation - this.adjustFrom.abs.rotation; let cx = to.x; let cy = to.y; this.calc2( adjustX, adjustY, adjustRotation, cx, cy ); } }//calc() calc2( adjustX, adjustY, adjustRotation, cx, cy ) { this.abs.x += adjustX; this.abs.y += adjustY; this.abs.rotation += adjustRotation; //adjust回転 let res = this.mathRotateC( this.abs.x, this.abs.y, cx, cy, adjustRotation ); this.abs.x = res.X; this.abs.y = res.Y; //各jointについてadjust回転 for( let name in this.abs.jointHash ) { let x = this.abs.jointHash[ name ].x + adjustX; let y = this.abs.jointHash[ name ].y + adjustY; let res = this.mathRotateC( x, y, cx, cy, adjustRotation ); this.abs.jointHash[ name ].x = res.X; this.abs.jointHash[ name ].y = res.Y; } //子について for( let i = 0; i < this.children.length; i++ ) { let child = this.children[ i ]; child.calc2( adjustX, adjustY, adjustRotation, cx, cy ); } } //SVC_Part draw( cc ) { //自身を描画 cc.save(); cc.translate( this.abs.x, this.abs.y ); cc.rotate( this.abs.rotation ); cc.translate( this.imgd.x, this.imgd.y ); this.imgd.draw( cc ); cc.restore(); //子を描画 for( let i = 0; i < this.children.length; i++ ) { let child = this.children[ i ]; child.draw( cc ); } //debug. if( this.debug1 ) { //各jointを○で示す for( let name in this.jointHash ) { let joint = this.abs.jointHash[ name ]; cc.save(); cc.translate( joint.x, joint.y ); cc.scale( 1, -1 ); cc.globalAlpha = .3; cc.beginPath(); cc.arc( 0, 0, 3, 0, 6.28, false ); cc.closePath(); cc.strokeStyle = "red"; cc.stroke(); cc.font = "6px''"; cc.fillText( name, 0, 0 ); cc.restore(); } } }//draw() //--- mathRotate( x, y, theta2 ) { let theta1 = Math.atan2( y, x ); let hankei = Math.sqrt( x * x + y * y ); return { X : Math.cos( theta1 + theta2 ) * hankei, Y : Math.sin( theta1 + theta2 ) * hankei, } } mathRotateC( x, y, cx, cy, theta2 ) { x -= cx; y -= cy; let theta1 = Math.atan2( y, x ); let hankei = Math.sqrt( x * x + y * y ); return { X : Math.cos( theta1 + theta2 ) * hankei + cx, Y : Math.sin( theta1 + theta2 ) * hankei + cy, } } }//SVC_Part class TestRect { constructor( type ) { this.x = 0; this.y = 0; this.w = 10; this.h = 10; this.visibility = true; this.type = type; } draw( cc ) { //check. if( ! this.visibility ) return; switch( this.type ) { case "rect": cc.strokeRect( 0, 0, this.w, this.h ); break; case "horizon": cc.fillStyle = "brown"; let w = 130; let h = 23; cc.fillRect( -w, -h, w * 2, h ); break; default: alert( "undefined type: '" + this.type + "'" ); } } }